'''

    DataK Base implementation

## TODO ##
    * Use generators whenver possible.
    make our own glob to generate path w/o the cnt.id
    
    * SortStrategy should accept where clause and not just attr name.
    This should not cost more if only attr names are given.
    
'''

import weakref
import os
import glob
import fnmatch
import re

#
#--- _KeySingleton
#
class _KeySingleton( object ):
    '''
    The _KeySingleton class holds weak references to its instances
    in order to make them singletons, based on the hashable object
    returned by _SingleKey.

    The arguments passed to _SingleKey are the one used in the
    constructor. Thus, subclass must use hashable objects only in their
    constructor or override _SingleKey.

    As weak reference are used, the instances will be deleted when not
    in use anymore.

    '''
    # weak refs to the singleton instances
    _singles = weakref.WeakValueDictionary()

    @classmethod
    def _SingleKey( klass, *args, **kwargs ):
        '''
        Return the key used to singletonize instances.
        Classes overriding __init__ signature must 
        override this to use the same signature as the new __init__.
        
        Retruning None means 'unic key', aka don't singletonize.
        '''
        return args, tuple(kwargs.items())

    def __new__( klass, *args, **kwargs ):
        '''
        Return the singleton instance, creating it if needed.
        '''
        key = klass._SingleKey( *args, **kwargs )
        if key is not None and key in klass._singles:
            return klass._singles[key]
        o = object.__new__( klass, *args, **kwargs )
        klass._singles[key] = o
        return o


#
#--- Revision Control
#
class RCError( RuntimeError ):
    pass
class RCStrategy( object ):
    '''
    RCStrategy stands for 'Revision Control Strategy'

    The default imp is to control nothing.
    See subclasses for more types revision control.

    Methods of this strategy is called before and after some
    actions on an item or item's attributes, and can be called
    arbitrarily at any time.
    Each methods will raise a RCError if the control cannot perform
    the operation.
    '''
    def __init__( self, user=None, needsLock=False ):
        self.user = None
        self.needsLock = needsLock

    def GetStatus( self, item, attrName ):
        return None

    def GetHistory( self, item, attrName ):
        return ()

    def GetLocker( self, item, attrName ):
        return None

    def IsLockedByMe( self, item, attrName ):
        return self.GetLocker( item, attrName ) == self.user

    def Lock( self, item, attrName ):
        pass

    def Update( self, item, attrName ):
        pass

    def Commit( self, item, attrName ):
        pass

    def BeforeGet( self, item, attrName ):
        pass

    def CanSet( self, item, attrName ):
        return not self.needsLock or self.IsLockedByMe()

    def BeforeSet( self, item, attrName ):
        if not self.CanSet( item, attrName ):
            raise RCError("Attribute not locked!")

    def AfterSet( self, item, attrName ):
        pass


#
#--- Attributes management
#
class AttributeStrategy( object ):
    '''
    Default strategy used to get, set and delete attributes of an Item. 
    '''
    def Get( self, item, attrName, herited=False, asFormula=False ):
        if herited:
            if item._id: # it's not a root
                parent = item._selecter._GetItem('..')
                return parent._attrs.Get( 
                    parent, attrName, herited=True, asFormula=asFormula 
                )
            return None
        path = os.path.join(item._path,attrName)
        if not os.path.exists(path):
            return None
        r = open( path, 'r' )
        content = r.read()
        r.close()
        if asFormula or not content.strip().startswith('='):
            return content
        #print '########### REDUCING', content
        exec( "v="+content )
        return v

    def Set( self, item, attrName, attrValue ):
        if (
            isinstance( attrValue, basestring )
            and attrValue.strip().startswith('=')
        ):
            if 'self.'+name in content:
                raise ValueError(
                    "Formula cannot reference its own attribute!"
                )
            content = pprint.pformat( attrValue )
        else:
            content = repr(attrValue)
        w = open( os.path.join(item._path,attrName), 'w' )
        w.write( content )
        w.close()

    def Del( self, item, attrName ):
        print 'DEL', attrName, 'from', item
        raise NotImplementedError
        
class LinkAttributeStrategy( AttributeStrategy ):
    '''
    Attribute strategy for Links: delegates to the targetItem attribute
    strategy.
    '''
    def Get( self, link, attrName, herited=False, asFormula=False ):
        return link._targetItem._attrs.Get(
            link._targetItem,
            attrName, herited=herited, asFormula=asFormula
        )

    def Set( self, link, attrName, attrValue ):
        return link._targetItem._attrs.Set(
            link._targetItem,
            attrName, attrValue
        )

    def Del( self, link, attrName ):
        return link._targetItem._attrs.Del(
            link._targetItem,
            link, attrName
        )

class GroupAttributeStrategy( AttributeStrategy ):
    '''
    Attribute strategy for Groups:
        * attibute's value is None uless all the group have the same value
        on this attribute.
        * set/del an attribute set/del it on every item of the group
    '''
    def DoOne( self, funcName, item, *args, **kwargs ):
        func = getattr( item._attrs, funcName )
        return func(
            item, *args, **kwargs
        )

    def Get( self, grp, attrName, herited=False, asFormula=False ):
        items = grp._items
        #for i in items:
        #    print i.name, i.groupable
        #print '$$$$$$$$$', attrName, '?'
        if not items:
            return None
        ret = self.DoOne(
            'Get', items[0], attrName, herited=herited, asFormula=asFormula
        )
        #print '---!', ret
        if ret is None:
            # one None is enought...
            return None
        for item in items[1:]:
            v = self.DoOne(
                'Get', item, attrName, herited=herited, asFormula=asFormula
            )
            if v != ret:
                return None
            #print '---?', ret
        return ret

    def Set( self, grp, attrName, attrValue ):
        for item in grp:
            self.DoOne( 'Set', item, attrName, attrValue )

    def Del( self, grp, attrName ):
        for item in grp:
            self.DoOne( 'Del', item, attrName )

#
#--- Select Strategy
#
class SelectError( RuntimeError ):
    pass
class SelectStrategy(object):
    '''
    Simple strategy to delegate item access and filtering.
    '''
    refRe = re.compile(
        '\@([a-zA-Z_]*)'
    )
    def _GetItem( self, item, path ):
        '''
        Get the item 'path' relative from item.
        path can be absolute but cannot use wilcards.
        '''
        if not path:
            return item # that's faster...
        
        id = path
        if not os.path.isabs( path ):
            id = os.path.join( item._id, path )
        if not item._cnt.IsItem( id ):
            raise SelectError( item._id+'->'+path, "No such item" )
        return item._cnt.GetItem( id )
    
    def _GetItems( self, item, path ):
        '''
        Get items relative from item.
        path can be absolute and use wilcards.
        '''
        if os.path.isabs( path ):
            # abs are really relative to the connection:
            path = os.path.join( item._cnt.GetPath(), path.strip('/') )
        else:
            path = os.path.join( item._path, path )
        
        ls = glob.glob( path )
        i = len(item._cnt.GetPath())
        ls = [ f[i:] for f in ls ]
        items = [ item._cnt.GetItem(f) for f in ls if item._cnt.IsItem(f) ]
        return items
    
    def _SelectItems( self, item, patterns=None ):
        '''
        Return a group of items relative from item as described
        in patterns.
        
        pattern must be one or several wilcarded ids:
            '*/status'
            ('enums/status/*','enums/type/*')
        If pattern is None, '*' is used.
        
        A SelectError will be raised if the pattern points
        to unexisting items.
        '''
        if patterns is None:
            patterns = ('*',)
        elif (
            isinstance( patterns, basestring)
            or not hasattr( patterns, '__iter__')
        ):
            patterns = (patterns,)
        
        items = []
        for pattern in patterns:
            items.extend( self._GetItems( item, pattern ) )
        
        return items
    
    def GetIndex( self, item, index ):
        # we must use SelectItems to have order and groups:
        return self.SelectItems( item, '*' )[index]
        
    def GetRange( self, item, start, stop, step ):
        # we must use SelectItems to have order and groups:
        return self.SelectItems( item, '*' )[start:stop:step]
        
    def SelectItems( self, item, patterns=None ):
        '''
        Return a sorted and grouped group of items relative 
        from item as described in patterns.
        
        pattern must be one or several wilcarded ids:
            '*/status'
            ('enums/status/*','enums/type/*')
        If pattern is None, '*' is used.
        
        A SelectError will be raised if the pattern points
        to unexisting items.
        '''
        items = self._SelectItems( item, patterns )
        items = item._sorter.Do( item, items, True )
        items = item._grouper.Do( item, items )
        return items

    def _Exec( self, code, refs ):
        '''
        Exec code as python code.
        code can use the dict 'refs' to use outerscope objects.
        '''
        #print '---EXEC', code, refs
        exec( 'ret = '+code )
        return ret

    def Where( self, item, where=None ):
        '''
        Return the evaluation of the python code in 'where' after
        replacing all referenced path by their corresponding item.
        
        '''
        if where is None:
            return True
            
        refs = {}
        def Reduce( match ):
            path = match.groups()[0]
            refs[path] = self._GetItem( item, path )
            return 'refs['+repr(path)+']'
        code = SelectStrategy.refRe.sub( Reduce, where )
        return self._Exec(code, refs)
        
    def Select( self, item, attrNames=None, patterns=None, where=None ):
        '''
        Return a group of items matching patterns and fullfiling where.
        If patterns is None, item is tested and its attributes are used.
        '''
        # we must use _SelectItems to have no order and no groups:
        items = self._SelectItems( item, patterns )
        items = [ i for i in items if self.Where( i, where ) ]
        items = item._sorter.Do( item, items, True )
        grp = item._grouper.Do( item, items )
        grp._SetColumns( attrNames )
        return grp

class GroupSelectStrategy(SelectStrategy):
    '''
    Group strategy to delegate item access and filtering.
    
    '''
    def _GetItem( self, grp, path ):
        '''
        Get the item 'path' in grp items.
        path can't use wilcards.
        Returns one item or raise a SelectError.
        '''
        path = os.path.normpath( path )
        for item in grp:
            if path == os.path.normpath(item._path):
                return item
        raise SelectError( 'Group->'+path, "No such item" )
    
    def _GetItems( self, grp, path ):
        '''
        Get items 'path' in grp items.
        'path' can use wilcards.
        Return a group of items matching path.
        '''
        path = os.path.normpath( path )
        ret = []
        for item in grp:
            ipath = os.path.normpath(item._path)
            if (
                isinstance( item, Group ) # recurse on groups !
                or
                fnmatch.fnmatch( ipath, path )
            ):
                ret.extend( 
                    item._selecter._GetItems( item, path )
                )
        return ret

    def GetIndex( self, grp, index ):
        return grp._items[index]
        
    def GetRange( self, grp, start, stop, step ):
        return grp._items[start:stop:step]


#
#--- Sort Strategy
#
class SortStrategy(object):
    '''
    Simple strategy to delegate sorting process.
    '''
    def __init__( self ):
        self.by = None
    
    def AddBy( self, attrName ):
        '''
        Add an attribut name to the sort by list.
        '''
        if self.by is None:
            self.by = []
        self.by.append( attrName )
        
    def SetBy( self, attrNameList=None ):
        '''
        Change the sort order.
        If attrNameList is None, default order will be used:
            by value then by name.
        If not None, it must be a list of attribute names.
        '''
        if attrNameList is None:
            # use default order
            self.by = None
            return
        if not hasattr( attrNameList, '__iter__' ):
            raise ValueError(
                "attrNameList must be None or iterable "
                "( not "+repr(attrNameList)+" )"
            )
        self.by = list( attrNameList )
        
    def Do( self, item, items, affectItems=True ):
        '''
        Reorder items accordingly to the last call to SetBy.
        If affectItems is True, every item will be set up to
        produce the same order style.
        'item' is the item requesting the sort. (not used...)
        '''
        sortBy = self.by
        if sortBy is None:
            sortBy = ()
        sortables = []
        for item in items:
            # affect only if requested:
            affectItems and item._sorter.SetBy( self.by )
            # now sort: 
            sortable = [ getattr(item,attrName) for attrName in sortBy ]
            sortable.append( item.value )
            sortable.append( item.name )
            sortable.append( item )
            sortables.append( sortable )
        sortables.sort()
        return [ sortable[-1] for sortable in sortables ]

#
#--- Group Strategy
#
class GroupStrategy(object):
    '''
    Simple strategy to delegate grouping process.
    '''
    def __init__( self ):
        self.by = None
    
    def SetBy( self, attrName=None ):
        self.by = attrName
    
    def Ungroup( self, item, items ):
        '''
        return all items but not the groups
        '''
        ret = []
        for i in items:
            if isinstance( i, Group ):
                ret.extend( i._grouper.Ungroup( i, i._items ) )
            else:
                ret.append( i )
        return ret
        
    def Do( self, item, items ):
        '''
        Group items (requested by item).
        '''
        if not items or self.by is None:
            return Group( item._cnt, *items )
        
        subGroups = []
        cnt = item._cnt
        last = getattr( items[0], self.by )
        currGrp = Group( cnt, items[0] )
        for i in items[1:]:
            v = getattr( i, self.by )
            if v != last:
                subGroups.append( currGrp )
                currGrp = Group( cnt )
                last = v
            currGrp += i
        subGroups.append( currGrp )
        if 1 == len( subGroups ):
            ret = subGroups[0]
        else:
            ret = Group( cnt, *subGroups )
        ret._grouper.by = self.by
        return ret

#
#--- _AbstractItem
#
class _AbstractItem( _KeySingleton ):
    '''
    The AbstractItem is an holder for items strategies.
    The instances are singletonized on the connection.id/id pairs.
    '''
    @classmethod
    def _SingleKey( klass, cnt, id ):
        '''
        Singletonize items on (cnt.id, id)
        '''
        return klass, cnt, id

    def __init__( self, cnt, id ):
        '''
        Create a new Item. You never want to call this.
        '''
        _KeySingleton.__init__( self )
        self._cnt = cnt
        self._id = id.strip('/')
        self._rc = cnt.GetRCStrategy(self)
        self._attrs = cnt.GetAttributeStrategy(self)
        self._selecter = cnt.GetSelectStrategy(self)
        self._sorter = SortStrategy()
        self._grouper = GroupStrategy()
    
    def __repr__( self ):
        return _KeySingleton.__repr__(self)[:-1]+' '+self.id+'>'
    
    def __str__( self ):
        return str( self.value )
    
    def __call__( self, expression=None ):
        '''
        item( expression )
        
        Evaluate the string expression and return the result.
        If expression is None, True is returned.
        
        An expression can wilcarded ids and '@' to represent the item:
            '@.attrName' <==> item.attrName
            '@SUB/*'     <==> item['SUB/*']
            '@/LIB'      <==> item['/LIB']
            '@[3:]       <==> item[3:]
            etc...
        and python code:
            '@.attrName * @SUB.attrName'
            '[ i.start_date for i in @TASK if i.user == "dee" ]'
        '''
        return self._selecter.Where( self, expression )
        
    def __getattr__( self, name ):
        if name == 'id':
            # mimic property
            return self._id
        elif name == '_path':
            # mimic property
            return self._cnt.GetPathFromId( self._id )
        elif name == 'name':
            # mimic property
            return os.path.basename(self._id)
        elif name == 'value':
            # overridable value attribute
            return self._attrs.Get( self, '.value' ) or self.name
        elif name in self.__dict__:
            return self.__getattribute__(name)
        if name.endswith('_'):
            return self._attrs.Get( self, name[-1], herited=True )
        return self._attrs.Get( self, name, herited=False )

    def __setattr__( self, name, value ):
        if name in self.__dict__ or name[0] == '_':
            return object.__setattr__( self, name, value )
        if name == 'value':
            name = '.value'
        self._attrs.Set( self, name, value )

    def __eq__( self, other ):
        if type(other) == type(self):
            return id(self) == id(other)
        return self.value == other
        
    def __iter__( self ):
        '''
        Iterate on children.
        '''
        # we must use SelectItems to have order and groups:
        return iter( self._selecter.SelectItems(self,'*') )

    def __lshift__(self, sortBy ):
        '''
        item << attrName << attrName
        
        Change the sort order used by methods returning items
        
        'sortBy' must be a list of attribute names, None, or a single
        attribute name:
            set the whole list:            item << ('attr1','attr2')
            reset to default (value,name): item << None
            add an attribute to sort on:   item << 'attrName'

        Returns self so you can do:
            item << 'name' << 'date'
        
        See class documentation for operator precedence order.
        
        '''
        if isinstance( sortBy, basestring):
            self._sorter.AddBy( sortBy )
        else:
            self._sorter.SetBy( sortBy )
        return self
    
    def __and__( self, groupBy ):
        '''
        item & attrName
        
        Change the way item are grouped by methods returning items.
        
        'groupBy' must be an attribute name or None:
            don't group:    item & None
            group by name:  item & 'name'
        '''
        self._grouper.SetBy( groupBy )
        return self
    
    def __mod__( self, attrName ):
        '''
        item % attrName
        
        Return the distinct values of the attribute attrName in 
        the item's children.
        '''
        raise NotImplementedError
        
    def __getitem__( self, select ):
        '''
        item[3]
        item[-1]
        item[2:4]
        item[2:-1]
        item[2:-1:2]
        item[:]
        item[attrNames]
        item[attrNames:fromItems]
        item[attrNames:fromItems:where]
        item[:items:where]
        item[:items]
        item[::where]
        
        Retreive a group of items ordered and grouped by the << and &
        operators previously used on this item.
        
        ** 'attrNames' can be a str or a list:
            item['name']
            item[('name','date')]
        To get the value of an herited, add a '_' at the end of its name:
            item['class']
            
            /!\ if present, the return value is a list of values.
            
        ** 'fromItems' and 'items' can be an wilcard or a list of wildcards:
            item['value':'enums/*']
            item[:('enums/status/*','enums/type/*')]
        If 'where' is given but not 'items' nor 'fromItems', the
        default value is used: '*'
                
        ** 'where' is a string of python code using referenced items:
            item[::'@status == "WIP"' ]
            item[::'@.dev == "dee"']
            item[::'"dee" in @.team']
            item[::'@.dev == "dee" and @status.int > 0']
            item[::'@.status == @/enums/status/default']
        Use '@' to mark a reference to another item, relative or absolute.
        Those references will be resolved before evaluating the code for
        each item found by 'items' or 'fromItems'. If the evaluation equals
        True, the item will be part of the returned group.
        if 'item' is given but not 'where', the default values is used: '1'
        
        '''
        fromItems, where = None, None
        if isinstance( select, int ):
            return self._selecter.GetIndex(self,select)
        elif isinstance( select, slice ):
            attrList = select.start
            fromItems = select.stop
            where = select.step
            if (
                isinstance(where,int)
                or
                ( isinstance(attrList,int) and isinstance(fromItems,int) )
            ):
                return self._selecter.GetRange(self,attrList,fromItems,where)
        elif isinstance( select, basestring ):
            attrList = [select]
            pattern = None
        else:
            try:
                attrList = [ str(i) for i in select ]
            except TypeError:
                raise TypeError("Bad select, must be a slice, str or str list")
        
        return self._selecter.Select( self, attrList, fromItems, where )
        
#
#--- Item
#
class Item( _AbstractItem ):
    '''
    A DataK Item.
    '''


#
#--- Link
#
class Link( _AbstractItem ):
    '''
    A DataK Link Item.
    
    It's like an Item, but attribute strategy is delegated to
    the target item
    '''
    @classmethod
    def _SingleKey( klass, cnt, id, target ):
        '''
        Singletonize items on (cnt.id, id)
        '''
        # we use the same klass as Item to avoid creating a Link AND
        # an Item on the same path.
        return _AbstractItem, cnt, id 

    def __init__( self, cnt, id, target ):
        _AbstractItem.__init__( self, cnt, id )
        self._target = target
        self._targetItem = cnt.GetItem( target )

    def __getattr__( self, name ):
        if name == 'id':
            # mimic property
            return self._id
        elif name == '_path':
            # mimic property
            return self._cnt.GetPathFromId( self._id )
        elif name == 'name':
            # mimic property
            return os.path.basename(self._id)
        elif name == 'value':
            # overridable value attribute
            return self._attrs.Get( self, '.value' ) or self.name
        elif name in self.__dict__:
            return self.__getattribute__(name)
        if name.endswith('_'):
            return self._attrs.Get( self, name[-1], herited=True )
        print '####', name
        return self._attrs.Get( self, name, herited=False )

    def __setattr__( self, name, value ):
        if name in self.__dict__ or name[0] == '_':
            return object.__setattr__( self, name, value )
        if name == 'value':
            name = '.value'
        self._attrs.Set( self, name, value )


#
#--- Group
#
# The Group is not a _AbstractItem because it is not Singletonized:
class Group( _AbstractItem ):
    '''
    A Group is a collection of items or groups.
    
    It's an Item with special behavior:
    * it has no id nor path and doesn't exits but in memory.
    * it is not a singleton, each Group instances are different.
    * iterating occurs on the items list
    * Setting an attribute on it sets on all the items in it.
    * Getting an attribute will retrun None until all items
    have the same value for this attribute.
    
    You can use += and -= to append and remove item(s):
        group += item           # add item in group
        group += group2         # add group2 in group
        group += (item1,item2)  # add item1 and item2 in group
        group += group2[:-1]    # add all items but the last one in group
        group += group2[:]      # add all items in group
        
        group -= item           # remove item from group (may raise)
        group -= group2         # remove group2 from group (may raise)
        group -= (item1,item2)  # remove item1 and item2 from group (may raise)
        group -= group2[:]      # remove item in group2 from group (may raise)
        
    '''
    @classmethod
    def _SingleKey( klass, cnt, *items ):
        '''
        Returns None to not singletonize Groups.
        '''
        return None
        
    def __init__( self, cnt, *items ):
        _AbstractItem.__init__( self, cnt, '' )
        self._columns = None
        self._items = list(items)
    
    def _SetColumns( self, attrNames ):
        self._columns = attrNames
        
    def __str__( self ):
        return str(self._grouper.by)
        
    def __nonzero__( self ):
        '''
        If group:
            print 'I has items'
        else:
            print 'No items'
        '''
        return __nonzero__(self._items)
    
    def __eq__( self, other):
        if type(other) == type(self):
            return other._items == self._items
        return self.value == other
        
    def __iter__( self ):
        if self._columns is None:
            return iter( self._items )
        return [ 
            [ getattr(i, attr) for attr in self._columns ] 
            for i in self._items 
        ]

    def __lshift__(self, sortBy ):
        '''
        group << attrName << attrName
        
        Change the order of grouped items
        Returns self so you can do:
            group << 'name' << 'date'
        But the sort is done on each call so it is faster to do:
            group << ('name', 'date')
            
        See Item documentation for more syntaxe details.
        
        '''
        if isinstance( sortBy, basestring):
            self._sorter.AddBy( sortBy )
        else:
            self._sorter.SetBy( sortBy )
        self._items = self._sorter.Do( self, self._items, False )
        return self
    
    def __and__( self, groupBy ):
        '''
        group & attrName
        
        Group the items in this group.
        '''
        ungrouped = self._grouper.Ungroup( self, self._items )
        self._grouper.SetBy( groupBy )
        self._items = self._grouper.Do( self, ungrouped )._items
        return self

    def __iadd__( self, other ):
        if isinstance( other, _AbstractItem ):
            # items/links/groups are added directly, not their content
            self._items.append( other )
        elif hasattr( other, '__iter__' ):
            self._items += other
        else:
            self._items.append( other )
        return self
    
    def __isub__( self, other ):
        if isinstance( other, _AbstractItem ):
            # items/links/groups are added directly, not their content
            self._items.remove( other )
        elif hasattr( other, '__iter__' ):
            [ self._items.remove(o) for o in other ]
        else:
            self._items.remove( other )
        return self
    
#
#--- Connection
#
class ConnectionError( ValueError ):
    pass
class Connection( _KeySingleton ):
    '''
    This is not an Item because you need real python attributes and
    methods.
    '''
    @classmethod
    def _SingleKey( klass, id, user ):
        return klass, id, user
        
    def __init__( self, id, user ):
        _KeySingleton.__init__( self, )
        if not os.path.isdir(id):
            raise ConnectionError("Bad id")
        self.id = id
        self.user = user
        
        self.rc = RCStrategy( user=self.user, needsLock=False )
        self.attrStrategies = {
            Item:  AttributeStrategy(),
            Link:  LinkAttributeStrategy(),
            Group: GroupAttributeStrategy(),
        }
        ss = SelectStrategy()
        self.selectStrategies = {
            Item: ss,
            Link:  ss,
            Group: GroupSelectStrategy(),
        }
        
        self._asItem = None
        
    def GetRCStrategy( self, item ):
        '''
        Return the strategy used by items as revision control.
        '''
        return self.rc
    
    def GetAttributeStrategy( self, item ):
        '''
        Return the strategy used by items to set/get/del attributes.
        '''
        return self.attrStrategies[item.__class__]
    
    def GetSelectStrategy( self, item ):
        '''
        Return the strategy used by items to select.
        '''
        return self.selectStrategies[item.__class__]
    
    def IsItem( self, id ):
        '''
        Return True if the given id exists under this connection.
        '''
        path = os.path.normpath( os.path.join( self.id, id.strip('/') ) )
        # I also check startswith to avoid ../../somewhere/else
        return (
            path.startswith( self.id ) 
            and (
                os.path.isdir( path )
                #or 
                #os.path.islink( path ) not needed since a link to a dir isdir.
            )
        )
    
    def GetPath( self ):
        '''
        Return the path of the connection in the filesystem.
        '''
        return self.id
    
    def GetPathFromId( self, id ):
        '''
        Return the path of the item with id 'id' on the filesystem.
        '''
        return os.path.join( self.id, id.strip('/') ) 
    
    def GetItem(self, id ):
        '''
        Get the item with the id 'id' under this connection.
        '''
        if not self.IsItem( id ):
            raise ConnectionError("Under "+self.id+", Bad Id: "+id)
        path = self.GetPathFromId( id )
        if os.path.islink( path ):
            trg = os.path.normpath( os.readlink(path) )
            if not trg.startswith( self.id ):
                #raise ConnectionError(
                #    "Link is pointing to Item outside connection:\n"
                #    +id+' -> '+trg
                #)
                pass
            return Link( self, id, trg[len(self.id):] )
        return Item( self, id )
            
    def _AsItem(self):
        '''
        Return an item representing the connection (empty id).
        A connection is not an item but sometime act like one
        using this method.
        '''
        if self._asItem is None:
            self._asItem = Item( self, '' )
        return self._asItem

    def __iter__( self ):
        '''
        => for i in connection:
               print i.name
        '''
        return iter( self._AsItem() )

    def __getitem__( self, select ):
        '''
        =>  connection[:3]
            connection['LIB']
            connection[:]
            etc... see Item.__getitem__
        '''
        return self._AsItem()[select]
























def MainTest():
    testServer = '/home/dee909/tmp/DTK'
    if 'MGL' in os.environ:
        testServer = '/u/local/dee/DTK'
    cnt = Connection( testServer, 'dee' )
    print 'CNT', cnt, [i for i in cnt]
    
    LIB = cnt.GetItem( 'LIB' )
    print 'LIB:', `LIB`
    LIB2 = cnt.GetItem( 'LIB' )
    print 'LIB:', `LIB2`
    if `LIB` != `LIB2`:
        raise Exception( "SINGLETON DIDNT WORK!" )

    distinctDates = LIB << 'name' << 'test' & 'date' 
    for group in LIB:
        print group.name, group.test

    print 20*'#'
    print 'LIB[2]',      LIB[2]
    print 'LIB[-1]',     LIB[-1]
    print 'LIB[1:3]',    [ str(i) for i in LIB[1:3] ]
    print 'LIB[1::2]',   [ str(i) for i in LIB[1::2] ]
    print 'LIB[1:-1:2]', [ str(i) for i in LIB[1:-1:2] ]
    print 'LIB[:]',      [ str(i) for i in LIB[:] ]
    print 'LIB["name"]', LIB['name']
    #return
    
    print 20*'#'
    print LIB[::'@.name == "CHARS"']

    print 20*'#'
    for i in LIB[:'CHARS/*/*':'@.name.endswith("0")']:
        print i.name
    
    print 20*'#'
    g = cnt[:]
    print repr(g), g[:]
    for i in g:
        print i.name

    print 20*'#'
    CHARS = cnt.GetItem('LIB/CHARS')
    print repr(CHARS)
    for i in CHARS:
        print i.name
        if False:
            i.groupable =  ( int(i.name[-2:]) +1 ) / 2
        print '  ', i.groupable
    
    LIB << 'groupable' & 'groupable'
    FAMILIES = LIB[:'CHARS/*']
    print '!!!!!!', FAMILIES
    #FAMILIES & 'value'
    print repr(FAMILIES), FAMILIES
    for g in FAMILIES:
        print 'GROUP #', FAMILIES
        for i in g:
            print '  ', i.name, i.value










def Test():
    testServer = '/home/dee909/tmp/DTK'
    if 'MGL' in os.environ:
        testServer = '/u/local/dee/DTK'
    cnt = Connection( testServer, 'dee' )
    
    LIB = cnt.GetItem( 'LIB' )
    print 'LIB[2]',LIB[2]
    print 'LIB[3]',      LIB[3]












if __name__ == '__main__':
    MainTest()
    #Test()
